// check if the build environment is Windows, and then include the Windows API header,
// else include SWELL which provides functions with the same names but are implemented
// for another operating system (namely, OS-X)

#ifdef _WIN32
#include <windows.h>
#else
#include "../../WDL/swell/swell.h"
#endif

#include "stdio.h"
#include <vector>
#include "lineparse.h"
#include <map>
// super nasty looking macro here but allows importing functions from Reaper with simple looking code
#define IMPAPI(x) if (!((*((void **)&(x)) = (void *)rec->GetFunc(#x)))) errcnt++;

#define REAPERAPI_DECL
#include "reaper_plugin_functions.h"

int g_registered_command01=0;
int g_registered_command02=0;
int g_registered_command03=0;
int g_registered_command04=0;
int g_registered_command05=0;

int g_rangeLearnActions[2];
int g_rangeLearnCounter=0;
bool g_rangeLearning=false;

std::map<int,int> g_paramSettingActions;

class FxParamActionRange
{
public:
    FxParamActionRange() :
        ptrproject(0), trackid(-2), fxid(-1),
        paramid(-1), firstactionid(-1), lastactionid(-1),
        paramlowvalue(0.0), paramhighvalue(1.0)
        {}
    bool Execute(int id) const
    {

        if (ptrproject && trackid>=-1 && (id>=firstactionid && id<=lastactionid))
        {
            MediaTrack* trackPtr=0;
            if (trackid>=0)
                trackPtr=GetTrack(ptrproject,trackid);// CSurf_TrackFromID(trackid,false);
            if (trackid==-1)
                trackPtr=GetMasterTrack(ptrproject);
            if (trackPtr)
            {
                //int fxnumber=m_effects.at(i).fxid;
                //int parid=m_effects.at(i).paramid;
                int actrange=lastactionid-firstactionid;
                double parrange=paramhighvalue-paramlowvalue;
                double val=(parrange/actrange*(id-firstactionid))+paramlowvalue;
                TrackFX_SetParamNormalized(trackPtr,fxid,paramid,val);
            }
            return true;
        }
        return false;

    }
    ReaProject* ptrproject;
    int trackid;
    int fxid;
    int paramid;
    int firstactionid;
    int lastactionid;
    double paramlowvalue;
    double paramhighvalue;
};

std::map<ReaProject*,std::vector<FxParamActionRange>> g_fxParamActionRanges;

// handle to the dll instance? could be useful for making win32 API calls
REAPER_PLUGIN_HINSTANCE g_hInst;

// global variable that holds the handle to the Reaper main window, useful for various win32 API calls
HWND g_parent;

// Action default keyboard shortcut and action text set here
// It would be best practice to not assign a default keyboard shortcut
// as the user might already have that same key assigned to something else etc
gaccel_register_t acreg01={{0,0,0},"Simple extension test action"};
gaccel_register_t acreg02={{0,0,0},"Simple extension togglable test action"};
gaccel_register_t acreg03={{0,0,0},"Xenakios : Render variations on selected effect of selected track"};
gaccel_register_t acreg04={{0,0,0},"Xenakios : FX Parameters : Start learn"};
gaccel_register_t acreg05={{0,0,0},"Xenakios : FX Parameters : Finish learn"};
// *** HERE THE ACTIONS DO THEIR WORK ***

void doAction1()
{
    MessageBox(g_parent,"Hello World!","Reaper extension API test",MB_OK);
}

// global variable to hold the toggle state of the togglable action
// each togglable action of course needs its own variable for this...

int g_togglestate; // will be inited in main() function to 0 or value from ini-file

void doAction2()
{
    // this action does nothing else but toggles the global variable that keeps track of the toggle state
    // so it's useless as such but you can see the action state changing in the toolbar buttons and the actions list
    if (g_togglestate==0)
        g_togglestate=1; // toggle state on
    else g_togglestate=0;
    // store new state of toggle action to ini file immediately
    char buf[8];
    // the REAPER api for ini-files only deals with strings, so form a string from the action
    // toggle state number.
    sprintf(buf,"%d",g_togglestate);
    SetExtState("simple_extension","toggleaction_state",buf,true);
}

void doAction3()
{
    int numSelTracks=CountSelectedTracks(0);
    int numSelItems=CountSelectedMediaItems(0);
    if (numSelTracks>0 && numSelItems>0)
    {
        MediaTrack* track=GetSelectedTrack(0,0);
        MediaItem* item=GetSelectedMediaItem(0,0);
        if (track && item)
        {
            MediaItem_Take* selectedTake=GetActiveTake(item);
            int indexOfSelectedEffect=TrackFX_GetChainVisible(track);
            if (indexOfSelectedEffect>=0)
            {
                const int numVariations=8;
                for (int i=0;i<numVariations;i++)
                {
                    SetActiveTake(selectedTake); // the apply fx action changes the active take, but we want to render using the originally active take
                    bool result=true;
                    if (i>0) // don't inc the preset for the first render
                    {
                        result=TrackFX_NavigatePresets(track, indexOfSelectedEffect, 1);
                    }
                    if (result==true) // i suppose false would mean the preset navigation failed
                    {
                        Main_OnCommand(40209,0);
                    }
                }
            }
        }
    }
}



// Reaper calls back to this when it wants to execute an action registered by the extension plugin
bool hookCommandProc(int command, int flag)
{
    // it might happen Reaper calls with 0 for the command and if the action
    // registration has failed the plugin's command id would also be 0
    // therefore, check the plugins command id is not 0 and then if it matches with
    // what Reaper called with
    if (g_registered_command01!=0 && command == g_registered_command01)
    {
        doAction1();
        return true;
    }
    if (g_registered_command02!=0 && command == g_registered_command02)
    {
        doAction2();
        return true;
    }
    if (g_registered_command03!=0 && command == g_registered_command03)
    {
        doAction3();
        return true;
    }
    if (g_registered_command04!=0 && command == g_registered_command04)
    {
        g_rangeLearning=true;
        g_rangeLearnCounter=0;
        g_rangeLearnActions[0]=-1;
        g_rangeLearnActions[1]=-1;
        return true;
    }
    if (g_registered_command05!=0 && command == g_registered_command05)
    {
        g_rangeLearning=false;
        int firstAct=g_rangeLearnActions[0];
        int lastAct=g_rangeLearnActions[1];
        if (firstAct==-1 || lastAct==-1)
            return true;
        firstAct=g_paramSettingActions[firstAct];
        lastAct=g_paramSettingActions[lastAct];
        if (lastAct<firstAct)
            std::swap(firstAct,lastAct);
        FxParamActionRange range;
        range.firstactionid=firstAct;
        range.lastactionid=lastAct;
        int numsteps=lastAct-firstAct+1;
        int tknumber=0;
        int fxnumber=0;
        int paramnumber=0;
        bool b=GetLastTouchedFX(&tknumber,&fxnumber,&paramnumber);
        if (b==true)
        {
            range.ptrproject=EnumProjects(-1,0,0);
            range.trackid=tknumber-1;
            range.fxid=fxnumber;
            range.paramid=paramnumber;
            g_fxParamActionRanges[range.ptrproject].push_back(range);
            char buf[200];
            sprintf(buf,"Added parameter action range %d %d %d\n",firstAct,lastAct,range.trackid);
            ShowConsoleMsg(buf);
        } else ShowConsoleMsg("No fx param was touched yet!\n");
        return true;
    }
    if (g_paramSettingActions.count(command)>0)
    {
        //ShowConsoleMsg("Action is in param setter command id map\n");
        if (g_rangeLearning==false)
        {
            int mapped=g_paramSettingActions[command];
            ReaProject* ptrproj=EnumProjects(-1,0,0);
            const std::vector<FxParamActionRange> v=g_fxParamActionRanges[ptrproj];
            for (int i=0;i<v.size();i++)
            {
                v.at(i).Execute(mapped);
            }
        } else
        {
            g_rangeLearnActions[g_rangeLearnCounter % 2]=command;
            g_rangeLearnCounter++;
        }
        return true;
    }
    // failed to run relevant action
    return false;
}

// Reaper calls back to this when it wants to know an actions's toggle state
int toggleActionCallback(int command_id)
{
    if (command_id && command_id==g_registered_command02)
        return g_togglestate;
    // -1 if action not provided by this extension or is not togglable
    return -1;
}

bool MyProcessExtensionLine(const char *line, ProjectStateContext *ctx, bool isUndo, struct project_config_extension_t *reg)
{
    if (isUndo==false)
    {
        //ShowConsoleMsg("Ripsu tahtoo ladata tilaa!\n");
        LineParser lp(false);
        if (lp.parse(line) || lp.getnumtokens() < 1)
            return false;
        if (strcmp(lp.gettoken_str(0), "<XENAKIOS_FXPARAMACTIONS") != 0)
            return false; // only look for <XENAKIOS_FXPARAMACTIONS lines
        std::vector<FxParamActionRange> v;
        char linebuf[4096];
        while(true)
        {
            if (!ctx->GetLine(linebuf,sizeof(linebuf)) && !lp.parse(linebuf))
            {
                if (lp.getnumtokens() > 0 && lp.gettoken_str(0)[0] == '>')
                    break;
                if (lp.getnumtokens() > 0 && strcmp(lp.gettoken_str(0),"PARAMRANGE")==0)
                {
                    int number=lp.gettoken_int(1);
                    int firstaction=lp.gettoken_int(2);
                    int secondaction=lp.gettoken_int(3);
                    int tracknum=lp.gettoken_int(4);
                    int fxnum=lp.gettoken_int(5);
                    int parnum=lp.gettoken_int(6);
                    double parlow=lp.gettoken_float(7);
                    double parhigh=lp.gettoken_float(8);
                    //char dbg[200];
                    //sprintf(dbg,"ParamRange %d %d %d\n",number,firstaction,secondaction);
                    //ShowConsoleMsg(dbg);
                    FxParamActionRange range;
                    range.ptrproject=EnumProjects(-1,0,0);
                    range.trackid=tracknum;
                    range.fxid=fxnum;
                    range.paramid=parnum;
                    range.firstactionid=firstaction;
                    range.lastactionid=secondaction;
                    range.paramlowvalue=parlow;
                    range.paramhighvalue=parhigh;
                    v.push_back(range);
                    g_fxParamActionRanges[range.ptrproject]=v;
                }

                //ml->m_items.Add(new MarkerItem(&lp));
            }
        }
        return true;
    }
    return false;
}

void MySaveExtensionConfig(ProjectStateContext *ctx, bool isUndo, struct project_config_extension_t *reg)
{
    if (isUndo==false)
    {
        //ShowConsoleMsg("Ripsu tahtoo tallettaa tilaa!\n");
        ctx->AddLine("<XENAKIOS_FXPARAMACTIONS");
        char buf[1024];
        ReaProject* ptrproj=EnumProjects(-1,0,0);
        const std::vector<FxParamActionRange> v=g_fxParamActionRanges[ptrproj];
        for (int i=0;i<v.size();i++)
        {
            //ctx->AddLine("<PARAMRANGE");
            const FxParamActionRange range=v.at(i);
            //if (range.ptrproject==EnumProjects(-1,0,0))
            //{
                sprintf(buf,"PARAMRANGE %d %d %d %d %d %d %f %f",i,range.firstactionid,range.lastactionid,range.trackid,range.fxid,range.paramid,
                        range.paramlowvalue,range.paramhighvalue);
                ctx->AddLine(buf);
            //}
            //ctx->AddLine(">");

        }
        ctx->AddLine(">");
    }
}

project_config_extension_t FxParamActionStateReg=
{
    MyProcessExtensionLine,
    MySaveExtensionConfig,
    0,
    0
};

extern "C"
{
// this is the only function that needs to be exported by a Reaper extension plugin dll
// everything then works from function pointers and others things initialized within this
// function
REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(REAPER_PLUGIN_HINSTANCE hInstance, reaper_plugin_info_t *rec)
{
    g_hInst=hInstance;
    if (rec)
    {
        if (rec->caller_version != REAPER_PLUGIN_VERSION || !rec->GetFunc)
            return 0;
        // initialize API function pointers from Reaper
        // REMEMBER to initialize the needed functions here, otherwise calling them results in a crash or worse!
        int errcnt=0; // this error counter variable will be "magically" incremented by the IMPAPI macro on errors
        IMPAPI(Main_OnCommand);
        IMPAPI(InsertMedia);
        IMPAPI(GetProjectPath);
        IMPAPI(GetResourcePath);
        IMPAPI(SetExtState);
        IMPAPI(GetExtState);
        IMPAPI(HasExtState);
        IMPAPI(TrackFX_GetChainVisible);
        IMPAPI(CountSelectedTracks);
        IMPAPI(GetSelectedTrack);
        IMPAPI(TrackFX_NavigatePresets);
        IMPAPI(CountSelectedMediaItems);
        IMPAPI(GetSelectedMediaItem);
        IMPAPI(GetMediaItemTake);
        IMPAPI(SetActiveTake);
        IMPAPI(GetActiveTake);
        IMPAPI(TrackFX_GetPresetIndex);
        IMPAPI(TrackFX_SetPresetByIndex);
        IMPAPI(ShowConsoleMsg);
        IMPAPI(GetLastTouchedFX);
        IMPAPI(TrackFX_SetParamNormalized);
        IMPAPI(CSurf_TrackFromID);
        IMPAPI(EnumProjects);
        IMPAPI(GetTrack);
        IMPAPI(GetMasterTrack);
        // if even one api function fails to initialize, it's not wise to continue, so abort plugin loading
        if (errcnt>0)
            return 0;
        acreg01.accel.cmd = g_registered_command01 = rec->Register("command_id",(void*)"EXAMPLE_ACTION_01");
        acreg02.accel.cmd = g_registered_command02 = rec->Register("command_id",(void*)"EXAMPLE_ACTION_02");
        acreg03.accel.cmd = g_registered_command03 = rec->Register("command_id",(void*)"XENAKIOS_RENDERFXVARIATIONS1");
        acreg04.accel.cmd = g_registered_command04 = rec->Register("command_id",(void*)"XENAKIOS_STARTPARAMACTIONLEARN");
        acreg05.accel.cmd = g_registered_command05 = rec->Register("command_id",(void*)"XENAKIOS_FINISHPARAMACTIONLEARN");
        if (!g_registered_command01) return 0; // failed getting a command id, fail!
        if (!g_registered_command02) return 0; // failed getting a command id, fail!
        if (!g_registered_command03) return 0; // failed getting a command id, fail!
        g_rangeLearnActions[0]=-1;
        g_rangeLearnActions[1]=-1;
        for (int i=0;i<64;i++)
        {
            gaccel_register_t acreg;
            char buf[100];//=new char[100];
            sprintf(buf,"Xenakios : Set FX parameter(s) %02d",i);
            acreg.accel.fVirt=0;
            acreg.accel.key=0;
            acreg.desc=strdup(buf);
            sprintf(buf,"XENAKIOS_FXSETPARAMACT_%d",i);
            int cmdid=rec->Register("command_id",(void*)buf);
            g_paramSettingActions[cmdid]=i;
            acreg.accel.cmd=cmdid;
            rec->Register("gaccel",(void*)&acreg);
        }
        rec->Register("gaccel",&acreg01);
        rec->Register("gaccel",&acreg02);
        rec->Register("gaccel",&acreg03);
        rec->Register("gaccel",&acreg04);
        rec->Register("gaccel",&acreg05);
        rec->Register("hookcommand",(void*)hookCommandProc);
        rec->Register("toggleaction",toggleActionCallback);
        rec->Register("projectconfig",(void*)&FxParamActionStateReg);
        g_parent = rec->hwnd_main;
        // restore extension global settings
        const char* numberString=GetExtState("simple_extension","toggleaction_state");
        g_togglestate=0;
        if (numberString)
            g_togglestate=atoi(numberString);
        // our plugin registered, return success

        return 1;
    }
    else
    {
        return 0;
    }
}

};

#ifndef _WIN32 // MAC resources
#include "../../WDL/swell/swell-dlggen.h"
#include "res.rc_mac_dlg"
#undef BEGIN
#undef END
#include "../../WDL/swell/swell-menugen.h"
#include "res.rc_mac_menu"
#endif
